import json
import logging
import os.path
import re
import subprocess
import time

import func_timeout.exceptions
from func_timeout import func_set_timeout

from resource.pom.keyboard import Keyboard
from resource.pom.launcher import Launcher
from utils.manager import MatchRules, Direction


class Device:
    def __init__(self, sn):
        self.sn = sn
        self.report_path = ''
        self.resource_path = ''
        self._element_list = []

    @func_set_timeout(30)
    def _execute_cmd(self, cmd):
        # with cls.lock:
        logging.info(f'[In]{cmd}')
        try:
            rst = subprocess.run(cmd, capture_output=True, shell=True, encoding='utf-8')
            out_put = rst.stdout or rst.stderr
        except:
            out_put = ''
        # 布局的回显太多了，不打印
        if not re.search(r'cat /data/local/tmp/\S+\.json', cmd):
            logging.info(f'[Out]{out_put}')
        if '[Fail]Device not founded or connected' in out_put:
            self.wait(5)
        self.wait(0.5)
        return out_put.strip()

    def execute_cmd(self, cmd):
        try:
            return self._execute_cmd(cmd)
        except func_timeout.exceptions.FunctionTimedOut:
            self.kill_hdc_process()
            self.wait(1)
            self.restart_hdc_process()
            self.wait(1)
            return self._execute_cmd(cmd)

    def hdc_shell(self, cmd):
        return self.execute_cmd(f'hdc -t {self.sn} shell "{cmd}"')

    def hdc(self, cmd):
        return self.execute_cmd(f'hdc -t {self.sn} {cmd}')

    def hdc_version(self):
        return self._execute_cmd('hdc -v')

    def hdc_list_targets(self):
        devices = self._execute_cmd('hdc list targets')
        if 'Empty' in devices:
            return []
        return devices.splitlines()

    def install_hap(self, hap_path, replace=True):
        """
        暗账应用
        :param hap_path: hap包的路径
        :param replace: 是否覆盖安装，true覆盖，否则不覆盖
        :return:
        """
        logging.info('安装hap包')
        if replace:
            cmd = f'app install -r {hap_path}'
        else:
            cmd = f'app install {hap_path}'
        return self.hdc(cmd)

    def install_multi_hap(self, hap_list: list):
        """
        安装更新, 多hap可以指定多个文件路径
        :param hap_list:
        :return:
        """
        logging.info('安装hap包')
        haps = ' '.join(hap_list)
        cmd = 'install {}'.format(haps)
        return self.hdc(cmd)

    def bm_install(self, hap_list: list):
        """
        bm工具安装更新多个hap
        :param hap_list:
        :return:
        """
        logging.info('安装hap包')
        haps = ' '.join(hap_list)
        cmd = 'bm install -p {}'.format(haps)
        return self.hdc_shell(cmd)

    def uninstall_hap(self, bundle_name):
        # 两个命令都可以
        logging.info(f'卸载{bundle_name}应用')
        # cmd = 'hdc app uninstall {}'.format(bundle_name)
        return self.hdc(f'uninstall {bundle_name}')

    def bm_uninstall(self, bundle_name):
        logging.info(f'卸载{bundle_name}应用')
        return self.hdc_shell(f'bm uninstall -n {bundle_name}')

    def hdc_file_send(self, local, remote):
        logging.info('发送文件')
        if not local and not remote:
            logging.info(f'本地路径{local}和设备路径{remote}不合法')
            return ''
        return self.hdc(f'file send "{local}" "{remote}"')

    def hdc_file_recv(self, remote, local=''):
        logging.info('接收文件')
        local = local or self.report_path
        if not remote:
            logging.info(f'设备路径{remote}不合法')
            return ''
        return self.hdc(f'file recv "{remote}" "{local}"')

    def hilog(self):
        logging.info('hilog')
        return self.hdc('hilog')

    def get_udid(self):
        logging.info('获取udid')
        return self.hdc('bm get --udid')

    def kill_hdc_process(self):
        logging.info('杀掉hdc进程')
        return self._execute_cmd('hdc kill')

    def restart_hdc_process(self):
        logging.info('重启hdc进程')
        return self._execute_cmd('hdc start -r')

    def reboot(self):
        logging.info('重启设备')
        self.hdc_shell('reboot')
        self.wait_boot_completed()

    def start_ability(self, bundle_name, ability_name):
        logging.info(f'打开{bundle_name}应用')
        for i in range(3):
            self.hdc_shell(f'aa start -b {bundle_name} -a {ability_name}')
            self.wait(2)
            win_name, _ = self.get_focus_window()
            if win_name != Launcher.entry_view:
                return win_name
            elif i == 2:
                return win_name

    def force_stop(self, bundle_name):
        logging.info(f'停掉{bundle_name}应用')
        return self.hdc_shell(f'aa force-stop {bundle_name}')

    def clean_app_data(self, bundle_name):
        """
        清除应用缓存和数据， -c缓存， -d应用数据
        :param bundle_name:
        :return:
        """
        if not bundle_name:
            return
        logging.info(f'清理{bundle_name}应用数据和缓存')
        return self.hdc_shell(f'bm clean -n {bundle_name} -c -d')

    def disable_app(self, bundle_name):
        """
        禁止应用，应用在桌面消失
        :param bundle_name:
        :return:
        """
        logging.info(f'禁止{bundle_name}应用，应用在桌面消失')
        return self.hdc_shell(f'bm disable -n {bundle_name}')

    def enable_app(self, bundle_name):
        """
        允许应用，应用显示在桌面上
        :param bundle_name:
        :return:
        """
        logging.info(f'允许{bundle_name}应用，应用显示在桌面上')
        return self.hdc_shell(f'bm enable -n {bundle_name}')

    def dump_hap_configuration(self, bundle_name):
        """
        查看应用配置信息
        :param bundle_name:
        :return:
        """
        logging.info(f'查看{bundle_name}应用配置信息')
        return self.hdc_shell(f'bm dump -n {bundle_name}')

    def stop_permission(self):
        win_name, _ = self.get_focus_window()
        if 'permissionDialog1' in win_name:
            logging.info('消掉权限请求的弹窗')
            self.refresh_layout()
            allow = self.get_element_by_condition(condition={'text': '允许', 'type': 'Button'})
            self.click_element(allow)
            self.wait(2)
        else:
            logging.info(f'当前窗口为{win_name}')

    def click(self, x: int, y: int):
        """
        模拟触摸按下
        :param x:
        :param y:
        :return:
        """
        logging.info(f'点击({x},{y})坐标')
        return self.hdc_shell(f'uitest uiInput click {x} {y}')

    def click_element(self, e):
        x, y = self.center_of_element(e)
        return self.click(x, y)

    def double_click(self, x, y):
        logging.info(f'双击({x},{y})坐标')
        return self.hdc_shell(f'uitest uiInput doubleClick {x} {y}')

    def double_click_element(self, e):
        x, y = self.center_of_element(e)
        return self.double_click(x, y)

    def long_click(self, x, y):
        logging.info(f'长按({x},{y})坐标')
        return self.hdc_shell(f'uitest uiInput longClick {x} {y}')

    def long_click_element(self, e):
        x, y = self.center_of_element(e)
        return self.long_click(x, y)

    def dirc_fling(self, direct=Direction.Left):
        """
        模拟指定方向滑动
        :param direct:direction (可选参数,滑动方向,可选值: [0,1,2,3], 滑动方向: [左,右,上,下],默认值: 0)
        swipeVelocityPps_ (可选参数,滑动速度,取值范围: 200-40000, 默认值: 600, 单位: px/s)
        stepLength(可选参数,滑动步长,默认值:滑动距离/50, 单位: px)
        :return:
        """
        direct_map = {
            0: '左',
            1: '右',
            2: '上',
            3: '下',
        }
        if direct not in direct_map.keys():
            direct = 0
        logging.info(f'向 {direct_map.get(direct)} 滑动')
        return self.hdc_shell(f'uitest uiInput dircFling {direct}')

    def swipe(self, from_x, from_y, to_x, to_y, velocity=2000):
        """
        模拟慢滑操作
        :param from_x:(必选参数,滑动起点x坐标)
        :param from_y:(必选参数,滑动起点y坐标)
        :param to_x:(必选参数,滑动终点x坐标)
        :param to_y:(必选参数,滑动终点y坐标)
        :param velocity: (可选参数,滑动速度,取值范围: 200-40000, 默认值: 600, 单位: px/s)
        :return:
        """
        # 保证数据取值范围合理
        logging.info(f'从({from_x},{from_y})滑动到({to_x},{to_y})，滑动速度：{velocity}px/s')
        return self.hdc_shell(f'uitest uiInput swipe {from_x} {from_y} {to_x} {to_y} {velocity}')

    def fling(self, from_x, from_y, to_x, to_y, velocity=2000):
        """
        模拟快滑操作
        :param from_x:(必选参数,滑动起点x坐标)
        :param from_y:(必选参数,滑动起点y坐标)
        :param to_x:(必选参数,滑动终点x坐标)
        :param to_y:(必选参数,滑动终点y坐标)
        :param velocity: (可选参数,滑动速度,取值范围: 200-40000, 默认值: 600, 单位: px/s)
        :return:
        """
        # 保证数据取值范围合理
        # 保证数据取值范围合理
        logging.info(f'从({from_x},{from_y})快速滑动到({to_x},{to_y})')
        return self.hdc_shell(f'uitest uiInput fling {from_x} {from_y} {to_x} {to_y} {velocity}')

    def drag(self, from_x, from_y, to_x, to_y, velocity=600):
        """
        拖拽，从（x1, y1）拖拽到（x2, y2）
       :param from_x:(必选参数,滑动起点x坐标)
        :param from_y:(必选参数,滑动起点y坐标)
        :param to_x:(必选参数,滑动终点x坐标)
        :param to_y:(必选参数,滑动终点y坐标)
        :param velocity: (可选参数,滑动速度,取值范围: 200-40000, 默认值: 600, 单位: px/s)
        """
        logging.info(f'从({from_x},{from_y})拖到({to_x},{to_y})，拖动速度：{velocity}px/s')
        return self.hdc_shell(f'uitest uiInput drag {from_x} {from_y} {to_x} {to_y} {velocity}')

    def key_event(self, key_code):
        logging.info(f'按下{key_code}键')
        return self.hdc_shell(f'uitest uiInput keyEvent {key_code}')

    def go_home(self):
        return self.key_event('Home')

    def go_back(self):
        return self.key_event('Back')

    def press_power_key(self):
        return self.key_event('Power')

    def press_recent_key(self):
        return self.click(514, 1243)

    def cancel_usb_dialog(self):
        return self.click(360, 800)

    def long_press_key(self, key_code: int, press_time=5000):
        """
        键盘长按事件
        :param key_code:https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/js-apis-keycode-0000001544703985-V2
        :param press_time:按压时间,单位 ms
        :return:
        """
        return self.hdc_shell(f'uinput -K -l {key_code} {press_time}')

    def long_press_power(self):
        return self.long_press_key(18)

    def clear_recent_task(self):
        logging.info('清理最近的任务')
        self.press_recent_key()
        self.wait(0.5)
        self.press_clear_btn()

    def press_clear_btn(self):
        self.click(360, 1170)

    def input_text(self, x, y, text=''):
        logging.info(f'向({x, y})坐标处输入“{text}”')
        return self.hdc_shell(f"uitest uiInput inputText {x} {y} '{text}'")

    def wakeup(self):
        logging.info('点亮屏幕')
        return self.hdc_shell('power-shell wakeup')

    def suspend(self):
        logging.info('熄灭屏幕')
        return self.hdc_shell('power-shell suspend')

    def set_power_mode(self, mode='602'):
        """
        设置电源模式
        :param mode:600 normal mode 正常模式
                    601 power save mode省电模式
                    602 performance mode性能模式,屏幕会常亮
                    603 extreme power save mode极端省电模式
        :return:
        """
        power_map = {
            '600': '正常模式',
            '601': '省电模式',
            '602': '性能模式',
            '603': '极端省电模式',
        }
        if mode not in power_map.keys():
            mode = '602'
        logging.info(f'设置电源为{power_map.get(mode)}')
        return self.hdc_shell(f'power-shell setmode {mode}')

    def display_screen_status(self):
        logging.info('获取屏幕点亮状态')
        power_rst = self.hdc_shell('hidumper -s 3308')
        state = int(re.search(r'State=(?P<State>\d+)', power_rst, re.I).group('State').strip())
        brightness = int(re.search(r'Brightness=(?P<Brightness>\d+)', power_rst, re.I).group('Brightness').strip())
        return {'state': state, 'brightness': brightness}

    def get_render_size(self):
        logging.info('获取屏幕分辨率')
        text = self.hdc_shell('hidumper -s RenderService -a screen')
        rst = re.search(r'render size: (?P<x>\d+)x(?P<y>\d+)', text, re.I)
        x = int(rst.group('x'))
        y = int(rst.group('y'))
        return x, y

    def dump_layout(self, file_name=''):
        logging.info('获取当前页面布局')
        if file_name:
            file_path = '/data/local/tmp/' + file_name
            dump_rst = self.hdc_shell(f'uitest dumpLayout -p {file_path}')
        else:
            dump_rst = self.hdc_shell(f'uitest dumpLayout')
        layout_file = dump_rst.split(':')[1].strip()
        return layout_file

    def save_layout_to_local(self, file_name=''):
        layout_file = self.dump_layout(file_name)
        self.hdc_file_recv(layout_file)

    def refresh_layout(self, file_name=''):
        """
        :param file_name: 文件名不包含路径
        :return:
        """
        self.wait(2)
        file_name = file_name or 'tmp_layout.json'
        tmp_file = self.dump_layout(file_name)
        json_data = json.loads(self.hdc_shell(f'cat {tmp_file}'))
        self._element_list = self._parse_attribute_nodes(json_data)
        # 将控件按从上到下，从左到右的顺序排列
        self._element_list.sort(key=lambda e: [self.center_of_element(e)[1], self.center_of_element(e)[0]])

    def snapshot_display(self, jpeg=''):
        """jpeg必须在/data/local/tmp目录"""
        logging.info('获取当前页面截图')
        if jpeg:
            jpeg = '/data/local/tmp/' + jpeg
            shot_rst = self.hdc_shell(f'snapshot_display -f {jpeg}')
        else:
            shot_rst = self.hdc_shell('snapshot_display')
        if 'success' not in shot_rst:
            return ''
        return re.search(r'write to (?P<snapshot>.*) as jpeg', shot_rst, re.I).group('snapshot').strip()

    def save_snapshot_to_local(self, file_name=''):
        tmp_file = self.snapshot_display(file_name)
        save_file = os.path.join(self.report_path, tmp_file.split('/')[-1])
        self.hdc_file_recv(tmp_file, self.report_path)
        return save_file

    def clear_local_tmp(self):
        self.hdc_shell('rm -rf /data/local/tmp/*')

    def set_screen_timeout(self, timeout=600):
        """
        设置屏幕超时时间
        :param timeout: 单位s
        :return:
        """
        logging.info(f'设置屏幕{timeout}s无操作后休眠')
        return self.hdc_shell(f'power-shell timeout -o {timeout * 1000}')

    def rm_faultlog(self):
        logging.info('删除fault log')
        self.hdc_shell('rm -f /data/log/faultlog/SERVICE_BLOCK*')
        self.hdc_shell('rm -f /data/log/faultlog/appfreeze*')
        self.hdc_shell('rm -f /data/log/faultlog/temp/cppcrash*')
        self.hdc_shell('rm -f /data/log/faultlog/faultlogger/jscrash*')
        self.hdc_shell('rm -f /data/log/faultlog/faultlogger/appfreeze*')
        self.hdc_shell('rm -f /data/log/faultlog/faultlogger/cppcrash*')

    def start_hilog(self):
        logging.info('开启hilog')
        self.hdc_shell('hilog -w stop;hilog -w clear')
        self.hdc_shell('hilog -r;hilog -b INFO;hilog -w start -l 10M -n 1000')

    def stop_and_collect_hilog(self, local_dir=''):
        logging.info('停止并收集hilog')
        self.hdc_shell('hilog -w stop')
        self.hdc_file_recv('/data/log/hilog/', local_dir)

    def unlock(self):
        """
        滑动解锁
        :return:
        """
        logging.info('解锁屏幕')
        return self.dirc_fling(Direction.Up)

    def assert_process_running(self, process):
        logging.info(f'检查{process}进程是否存在')
        rst = self.hdc_shell(f'ps -ef | grep -w {process} | grep -v grep')
        assert process in rst, f'进程{process}不存在'

    def get_pid(self, process):
        logging.info(f'获取{process}进程PID')
        return self.hdc_shell(f'pidof {process}').strip()

    def get_wifi_status(self):
        # hidumper -ls查看所有hidumper服务
        logging.info('获取wifi状态')
        text = self.hdc_shell('hidumper -s WifiDevice')
        active_state = re.search(r'WiFi active state: (?P<active_state>.*)', text, re.I).group('active_state').strip()
        connection_status = re.search(r'WiFi connection status: (?P<connection_state>.*)', text, re.I).group('connection_state').strip()

        return {
            'active': active_state,
            'connected': connection_status
        }

    def dump_windows_manager_service(self):
        rst = self.hdc_shell("hidumper -s WindowManagerService -a '-a'")
        return rst

    def dump_window_inspector(self, win_id, win_area):
        logging.info(f'获取{win_id}的窗口控件信息')
        node_data = self.hdc_shell(f"hidumper -s WindowManagerService -a '-w {win_id} -inspector'")

        self._element_list = []
        node = {}
        for line in node_data.splitlines():
            if re.search(r'->\s+\S+\s+childSize:', line, re.I):
                _type = re.search(r'->\s+(?P<type>\S+)\s+childSize:', line, re.I).group('type')
                node.update({'type': _type})
            elif re.search(r'compid:\s*\S*', line, re.I):
                key = re.search(r'compid:\s*(?P<key>\S*)', line, re.I).group('key')
                node.update({'key': key})
            elif re.search(r'text:\s*\S*', line, re.I):
                text = re.search(r'text:\s*(?P<text>\S*)', line, re.I).group('text')
                node.update({'text': text})
            elif re.search(r'top:\s*\S*', line, re.I):
                top = int(float(re.search(r'top:\s*(?P<top>\S*)', line, re.I).group('top')))
                node.update({'top': top})
            elif re.search(r'left:\s*\S*', line, re.I):
                left = int(float(re.search(r'left:\s*(?P<left>\S*)', line, re.I).group('left')))
                left = left if left < win_area['x1'] else left - win_area['x1']
                node.update({'left': left})
            elif re.search(r'width:\s*\S*', line, re.I):
                width = int(float(re.search(r'width:\s*(?P<width>\S*)', line, re.I).group('width')))
                node.update({'width': width})
            elif re.search(r'height:\s*\S*', line, re.I):
                height = int(float(re.search(r'height:\s*(?P<height>\S*)', line, re.I).group('height')))
                node.update({'height': height})
            elif re.search(r'visible:\s*\S*', line, re.I):
                visible = int(re.search(r'visible:\s*(?P<visible>\S*)', line, re.I).group('visible'))
                node.update({'visible': visible})
            elif re.search(r'checked:\s*\S*', line, re.I):
                checked = re.search(r'checked:\s*(?P<checked>\S*)', line, re.I).group('checked')
                checked = 'true' if checked == '1' else 'false'
                node.update({'checked': checked})
            elif re.search(r'hint:\s*\S*', line, re.I):
                hint = re.search(r'hint:\s*(?P<hint>\S*)', line, re.I).group('hint')
                node.update({'hint': hint})
            elif re.search(r'childTree:', line, re.I):
                current_node = node.copy()
                if win_area['y0'] <= current_node['top'] <= win_area['y1'] and current_node['visible']:
                    self._element_list.append(current_node)
                node.clear()

    def get_focus_window(self, wms_output=''):
        if not wms_output:
            self.wait(2)
            text = self.dump_windows_manager_service()
        else:
            text = wms_output
        win_id = ''
        win_name = ''
        matched_win_id = re.search(r'Focus window:\s+(?P<WinId>\d+)', text)
        if matched_win_id:
            win_id = matched_win_id.group('WinId')
        matched_win_name = re.search(r'(?P<WindowName>\w+)\s+(\d+\s+){2}' + win_id + r'\s+(\d+\s+){5}', text)
        if matched_win_name:
            win_name = matched_win_name.group('WindowName')
        logging.info('当前聚焦的窗口WinId：{}'.format(win_id))
        logging.info('当前聚焦的窗口WindowName：{}'.format(win_name))
        return win_name, win_id

    def assert_current_window(self, window_name):
        focused_win, _ = self.get_focus_window()
        assert focused_win.startswith(window_name), f'当前窗口 {focused_win} 不以{window_name}开头'

    def get_navigationb_winid(self):
        text = self.dump_windows_manager_service()
        match_rst = re.search(r'SystemUi_NavigationB\s+(\d+\s+){2}(?P<win_id>\d+)', text)
        return match_rst.group('win_id')

    def get_statusbar_winid(self):
        # 状态栏
        text = self.dump_windows_manager_service()
        match_rst = re.search(r'SystemUi_StatusBar\s+(\d+\s+){2}(?P<win_id>\d+)', text)
        return match_rst.group('win_id')

    def close_soft_keyboard(self):
        if self.is_soft_keyboard_on():
            self.click(*Keyboard.close)

    def is_soft_keyboard_on(self):
        if self.soft_keyboard_area():
            return True
        return False

    def keyboard_mode(self):
        self.refresh_layout()
        keyboard = self.get_element_by_condition(condition={'bundleName': Keyboard.bundle_name})
        winid = keyboard.get('hostWindowId')
        if self.get_element_by_condition(condition={'hostWindowId': winid, 'text': 'ABC'}):
            return 'numpad'
        return 'alphabet'
        # if self.get_element_by_condition(condition={'hostWindowId': winid, 'text': '?123'}):
        #     return 'alphabet'

    def assert_soft_keyboard_on(self, expect=True):
        logging.info('检查软键盘是否拉起或收起')
        if expect:
            assert self.soft_keyboard_area(), '软键盘未拉起'
        else:
            assert not self.soft_keyboard_area(), '软键盘未收起'

    def soft_keyboard_area(self):
        return self.get_window_properties(Keyboard.win_name, self.dump_windows_manager_service())

    def systemui_statusbar_properties(self, wms_output):
        return self.get_window_properties(Launcher.statusbar, wms_output)

    def systemui_navigationb_properties(self, wms_output):
        return self.get_window_properties(Launcher.navigationb, wms_output)

    def refresh_current_window_elements(self):
        wms_output = self.dump_windows_manager_service()
        win_name, win_id = self.get_focus_window(wms_output)
        statusbar = self.systemui_statusbar_properties(wms_output)
        current_win = self.get_window_properties(win_name, wms_output)
        navigationb = self.systemui_navigationb_properties(wms_output)
        touch_area = {
            'x0': current_win.get('x', 0),
            'y0': current_win.get('y', 0),
            'x1': current_win.get('x', 0) + current_win.get('w', 720),
            'y1': current_win.get('y', 0) + current_win.get('h', 1280),
        }
        if statusbar and touch_area['y0'] < (statusbar['y'] + statusbar['h']):
            touch_area['y0'] = statusbar['y'] + statusbar['h']
        if navigationb and touch_area['y1'] > (navigationb['y']):
            touch_area['y1'] = navigationb['y']
        self.dump_window_inspector(win_id, touch_area)

    def get_window_properties(self, win_name, wms_output):
        if not win_name:
            return {}
        if not wms_output:
            wms_output = self.dump_windows_manager_service()
        pattern = (r'\s+(?P<DisplayId>\d+)\s+(?P<Pid>\d+)\s+(?P<WinId>\d+)\s+(?P<Type>\d+)\s+(?P<Mode>\d+)\s+(?P<Flag>\d+)\s+(?P<ZOrd>\d+)\s+(?P<Orientation>\d+)\s+\[\s+('
                   r'?P<x>\d+)\s+(?P<y>\d+)\s+(?P<w>\d+)\s+(?P<h>\d+)\s+]')
        matched_win = re.search(win_name + pattern, wms_output)
        if not matched_win:
            return
        else:
            return {
                'DisplayId': int(matched_win.group('DisplayId')),
                'Pid': int(matched_win.group('Pid')),
                'WinId': int(matched_win.group('WinId')),
                'Type': int(matched_win.group('Type')),
                'Mode': int(matched_win.group('Mode')),
                'Flag': int(matched_win.group('Flag')),
                'ZOrd': int(matched_win.group('ZOrd')),
                'Orientation': int(matched_win.group('Orientation')),
                'x': int(matched_win.group('x')),
                'y': int(matched_win.group('y')),
                'w': int(matched_win.group('w')),
                'h': int(matched_win.group('h')),
            }

    def get_elements_by_text(self, text, rule=MatchRules.Equal):
        logging.info(f'获取(text={text}, 匹配规则={rule})的控件')
        ems = []
        for e in self._element_list:
            if rule == MatchRules.Equal and e.get('text') == text:
                ems.append(e)
            elif rule == MatchRules.Contain and text in e.get('text'):
                ems.append(e)
        return ems

    def get_element_by_text(self, text, index=0, rule=MatchRules.Equal):
        ems = self.get_elements_by_text(text, rule)
        if not ems:
            logging.info(f'(text={text}，匹配规则={rule})的控件不存在')
            return
        logging.info('控件信息：{}'.format(ems[index]))
        return ems[index]

    def assert_text_exist(self, text, rule=MatchRules.Equal, expect=True):
        element = self.get_elements_by_text(text, rule)
        rst = '是' if element else '否'
        logging.info(f'检查(text={text}，匹配规则={rule})的控件是否存在？[{rst}]')
        if expect:
            assert element, '控件不存在，与预期结果不符'
        else:
            assert not element, '控件存在，与预期结果不符'

    def get_elements_by_type(self, _type, rule=MatchRules.Equal):
        logging.info(f'获取(type={_type}，匹配规则={rule})的控件')
        ems = []
        for e in self._element_list:
            if rule == MatchRules.Equal and e.get('type') == _type:
                ems.append(e)
            elif rule == MatchRules.Contain and _type in e.get('type'):
                ems.append(e)
        return ems

    def get_element_by_type(self, _type, index=0, rule=MatchRules.Equal):
        ems = self.get_elements_by_type(_type, rule)
        if not ems:
            logging.info(f'(type={_type}，匹配规则={rule})的控件不存在')
            return
        logging.info('控件信息：{}'.format(ems[index]))
        return ems[index]

    def assert_type_exist(self, _type, rule=MatchRules.Equal, expect=True):
        element = self.get_elements_by_type(_type, rule)
        rst = '是' if element else '否'
        logging.info(f'检查(type={_type}，匹配规则={rule})是否存在？[{rst}]')
        if expect:
            assert element, '控件不存在，与预期结果不符'
        else:
            assert not element, '控件存在，与预期结果不符'

    def get_elements_by_key(self, key, rule=MatchRules.Equal):
        logging.info(f'获取(key={key}，匹配规则={rule})的控件')
        ems = []
        for e in self._element_list:
            if rule == MatchRules.Equal and e.get('key') == key:
                ems.append(e)
            elif rule == MatchRules.Contain and key in e.get('key'):
                ems.append(e)
        return ems

    def get_element_by_key(self, key, index=0, rule=MatchRules.Equal):
        ems = self.get_elements_by_key(key, rule)
        if not ems:
            logging.info(f'(key={key}，匹配规则={rule})的控件不存在')
            return
        logging.info('控件信息：{}'.format(ems[index]))
        return ems[index]

    def assert_key_exist(self, key, rule=MatchRules.Equal, expect=True):
        element = self.get_elements_by_key(key, rule)
        rst = '是' if element else '否'
        logging.info(f'检查(key={key}，匹配规则={rule})的控件是否存在？[{rst}]')
        if expect:
            assert element, '控件不存在，与预期结果不符'
        else:
            assert not element, '控件存在，与预期结果不符'

    def get_elements_by_condition(self, condition: dict):
        logging.info(f'获取(条件={condition})的控件')
        ems = []
        for e in self._element_list:
            cs = set(condition.items())
            if cs.issubset(set(e.items())):
                ems.append(e)
        return ems

    def get_element_by_condition(self, condition, index=0):
        ems = self.get_elements_by_condition(condition)
        if not ems:
            logging.info(f'(条件={condition})的控件不存在')
            return
        logging.info('控件信息：{}'.format(ems[index]))
        return ems[index]

    def assert_element_exist(self, condition, expect=True):
        element = self.get_elements_by_condition(condition)
        rst = '是' if element else '否'
        logging.info(f'检查(条件={condition})的控件是否存在？[{rst}]')
        if expect:
            assert element, '控件不存在，与预期结果不符'
        else:
            assert not element, '控件存在，与预期结果不符'

    def set_brightness(self, value=102):
        return self.hdc_shell(f'power-shell display -s {value}')

    def factory_reset(self):
        """
        恢复出厂设置
        :return:
        """
        logging.info('恢复出厂设置')
        # self.hdc_shell('rm -rf /data/*;reboot')
        self.hdc_shell('write_updater user_factory_reset;reboot updater')
        self.wait_boot_completed()

    def center_of_element(self, e):
        x1, y1, x2, y2 = self.get_element_area(e)
        x = (x1 + x2) // 2
        y = (y1 + y2) // 2
        return x, y

    @staticmethod
    def get_element_area(e, orig=False):
        assert e, '控件不存在'
        bounds = e.get('origBounds') if orig else e.get('bounds')
        result = re.search(r'(?P<x1>\d+),(?P<y1>\d+)]\[(?P<x2>\d+),(?P<y2>\d+)', bounds)
        x1, y1, x2, y2 = (int(i) for i in result.groups())
        return x1, y1, x2, y2

    def _parse_attribute_nodes(self, json_obj, attr_list=None):
        if attr_list is None:
            attr_list = []

        if isinstance(json_obj, dict):
            for key, value in json_obj.items():
                if key == 'attributes' and isinstance(value, dict):
                    attr_list.append(value)
                elif isinstance(value, (dict, list)):
                    self._parse_attribute_nodes(value, attr_list)
        elif isinstance(json_obj, list):
            for item in json_obj:
                self._parse_attribute_nodes(item, attr_list)
        return attr_list

    def dropdown_notification_bar(self):
        """下拉通知面板"""
        logging.info('下滑呼出通知面板')
        self.swipe(200, 1, 200, 120)

    def dropdown_controller_center(self):
        """下拉控制中心"""
        logging.info('下滑呼出控制中心')
        self.swipe(500, 1, 500, 120)

    def swipe_up_dropdown_pan(self):
        """
        收回通知面板或控制中心
        :return:
        """
        logging.info('上滑收起控制中心或通知面板')
        self.swipe(360, 600, 360, 500)

    def set_param(self, param, value):
        rst = self.hdc_shell(f'param set {param} {value}')
        self.hdc_shell('param save')
        return rst

    def get_param(self, param):
        return self.hdc_shell(f'param get {param}').strip()

    def mediatool_delete_all(self):
        logging.info('删除预置资源')
        return self.hdc_shell('mediatool delete all')

    def mediatool_send(self, device_path):
        return self.hdc_shell(f'mediatool send "{device_path}"')

    @staticmethod
    def wait(wait_time):
        time.sleep(wait_time)

    def is_audio_running(self):
        rst = self.hdc_shell('hidumper -s AudioDistributed')
        if 'Status:STOPPED/PAUSED' in rst:
            return False
        return True

    def get_volume(self):
        rst = self.hdc_shell('hidumper -s AudioPolicyService')
        # 声音模式
        ringer_mode = re.search(r'Ringer Mode:\s*(?P<volume>\S*)', rst).group('volume')
        # 通话 call=communication
        call = int(re.search(r'- VOICE_CALL:\s*(?P<volume>\d+)', rst).group('volume'))
        # 音乐（媒体）music=wakeup=camcorder
        music = int(re.search(r'- MUSIC:\s*(?P<volume>\d+)', rst).group('volume'))
        # 来电
        ring = int(re.search(r'- RING:\s*(?P<volume>\d+)', rst).group('volume'))
        assistant = int(re.search(r'- VOICE_ASSISTANT:\s*(?P<volume>\d+)', rst).group('volume'))
        # 闹铃（媒体）
        wakeup = int(re.search(r'- WAKEUP:\s*(?P<volume>\d+)', rst).group('volume'))
        # 通话
        communication = int(re.search(r'- VOICE_COMMUNICATION:\s*(?P<volume>\d+)', rst).group('volume'))
        # 录像（媒体）
        camcorder = int(re.search(r'- CAMCORDER:\s*(?P<volume>\d+)', rst).group('volume'))
        return {
            "ringer_mode": ringer_mode,
            "call": call,
            "music": music,
            "ring": ring,
            "assistant": assistant,
            "wakeup": wakeup,
            "communication": communication,
            "camcorder": camcorder,
        }

    def pinch_with_both_fingers(self, p0: tuple, p1: tuple, p2: tuple, p3: tuple):
        """
        双指捏合放大缩小操作p0->p1, p2->p3
        :param p0:
        :param p1:
        :param p2:
        :param p3:
        :return:
        """
        x0, y0 = p0
        x1, y1 = p1
        x2, y2 = p2
        x3, y3 = p3
        cmd = f'uinput -T -m {x0} {y0} {x1} {y1} {x2} {y2} {x3} {y3}'
        return self.hdc_shell(cmd)

    def wait_boot_completed(self):
        for i in range(60):
            output = self.get_param('bootevent.boot.completed')
            if 'true' in output:
                return
            time.sleep(5)

    def is_element_by_text_in_area(self, text, x1, y1, x2, y2):
        elements = self.get_elements_by_text(text)
        for element in elements:
            x, y = self.center_of_element(element)
            if x1 < x < x2 and y1 < y < y2:
                return True
        return False
